#include <windows.h>
#include <wininet.h>
#include <iostream>
#include <string>
#include <sstream>
#include <format>
#include <string_view>
#include <memory>
#include <print>
#include <fstream>
using namespace std;

#define ERR(fun, code) ErrorMessage(__FILE__, __LINE__, __FUNCSIG__, fun, code)
#define E(fun) throw ERR(fun, GetLastError())
string ErrorMessage(string_view filename, unsigned int line, string_view parent_function, string_view function, unsigned long dwCode); // 输出错误信息

void PrintNetErr();

class Url
{
public:
    Url(string_view url) : m_hOpen(nullptr), m_hConnect(nullptr), m_hRequest(nullptr)
    {
        m_hOpen = InternetOpenW(nullptr, INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0);
        if (!m_hOpen)
            E("InternetOpenW");
        URL_COMPONENTSA url_c = {sizeof(URL_COMPONENTSA)};
        url_c.dwHostNameLength = 1;
        url_c.dwExtraInfoLength = 1;
        url_c.dwUserNameLength = 1;
        url_c.dwPasswordLength = 1;
        url_c.dwUrlPathLength = 1;
        if (!InternetCrackUrlA(url.data(), 0, 0, &url_c))
            E("InternetCrackUrlA");
        string host_name;
        string user_name;
        string password;
        string objname;
        host_name.append(url_c.lpszHostName, url_c.lpszHostName + url_c.dwHostNameLength);
        user_name.append(url_c.lpszUserName, url_c.lpszUserName + url_c.dwUserNameLength);
        password.append(url_c.lpszPassword, url_c.lpszPassword + url_c.dwPasswordLength);
        objname.append(url_c.lpszUrlPath, url_c.lpszUrlPath + url_c.dwUrlPathLength);
        objname.append(url_c.lpszExtraInfo, url_c.lpszExtraInfo + url_c.dwExtraInfoLength);
        DWORD service = 0;
        DWORD dwFlag = 0;
        switch (url_c.nScheme)
        {
        case INTERNET_SCHEME_FTP:
            service = INTERNET_SERVICE_FTP;
            break;
        case INTERNET_SCHEME_GOPHER:
            service = INTERNET_SERVICE_GOPHER;
            break;
        case INTERNET_SCHEME_HTTPS:
            dwFlag = INTERNET_FLAG_SECURE;
        case INTERNET_SCHEME_HTTP:
            service = INTERNET_SERVICE_HTTP;
            break;
        }
        m_hConnect = InternetConnectA(m_hOpen, host_name.c_str(), url_c.nPort, user_name.c_str(), password.c_str(), service, 0, 0);
        if(!m_hConnect)
        {
            DWORD dwErrCode = GetLastError();
            PrintNetErr();
            throw ERR("InternetConnectA", dwErrCode);
        }
        m_hRequest = HttpOpenRequestA(m_hConnect, nullptr, objname.c_str(), nullptr, nullptr, nullptr, dwFlag, 0);
        if (!m_hRequest)
            E("HttpOpenRequestA");
        if(!HttpSendRequestW(m_hRequest, nullptr, 0, nullptr, 0))
            E("HttpOpenRequestA");
    }
    ~Url()
    {
        if (m_hRequest)
            InternetCloseHandle(m_hRequest);
        if (m_hConnect)
            InternetCloseHandle(m_hConnect);
        if (m_hOpen)
            InternetCloseHandle(m_hOpen);
    }
    Url(const Url &) = delete;
    Url(Url &&o) : m_hOpen(o.m_hOpen), m_hConnect(o.m_hConnect), m_hRequest(o.m_hRequest)
    {
        o.m_hOpen = nullptr;
        o.m_hConnect = nullptr;
        o.m_hRequest = nullptr;
    }
    Url &operator=(const Url &) = delete;

private:
    HINTERNET m_hOpen;
    HINTERNET m_hConnect;
    HINTERNET m_hRequest;
    friend ostream &operator<<(ostream &out, Url &&url);
};

ostream &operator<<(ostream &out, Url &&url)
{
    DWORD dwSize = 0;
    do
    {
        if (!InternetQueryDataAvailable(url.m_hRequest, &dwSize, 0, 0))
            E("InternetQueryDataAvailable");
        else
        {
            unique_ptr<char[]> buf(new char[dwSize]);
            DWORD dwRead;
            if (!InternetReadFile(url.m_hRequest, buf.get(), dwSize, &dwRead))
            {
                E("InternetReadFile");
                dwSize = 0;
            }
            else
            {
                out.write(buf.get(), dwRead);
            }
        }
    } while (dwSize);
    return out;
}

int main(int argc, char **argv)
{
    if (argc < 2 || argc > 3)
    {
        println(stderr, "usage: {} url [output filepath]", argv[0]);
        return 1;
    }

    try
    {
        if (argc == 2)
            cout << Url(argv[1]) << endl;
        else
        {
            ofstream file(argv[2]);
            file << Url(argv[1]);
        }
    }
    catch (const string &s)
    {
        println(stderr, "{}", s);
    }
}

void PrintNetErr()
{
    DWORD dwLen = 0;
    DWORD dwError = 0;
    InternetGetLastResponseInfoA(NULL, NULL, &dwLen);
    unique_ptr<char[]> buf(new char[dwLen + 1]);
    InternetGetLastResponseInfoA(&dwError, buf.get(), &dwLen);
    println(stderr, "error: {}, buffer: {}", dwError, buf.get());
}

string ErrorMessage(string_view filename, unsigned int line, string_view parent_function, string_view function, unsigned long dwCode) // 输出错误信息
{
    LPSTR lpMsgBuf = nullptr;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS |
            FORMAT_MESSAGE_MAX_WIDTH_MASK,
        NULL,
        dwCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&lpMsgBuf,
        0, NULL);

    if (!lpMsgBuf)
        return "";
    unique_ptr<char, HLOCAL(WINAPI *)(HLOCAL)> _(lpMsgBuf, LocalFree);

    return format(
        "Filename: {}\n"
        "Line: {}\n"
        "Parent: {}\n"
        "Function: {}\n"
        "Code: {}\n"
        "Description: {}\n",
        filename, line, parent_function, function, dwCode, lpMsgBuf);
}